What is AOP

Aspect-Oriented Programming (AOP) is a programming paradigm that complements Object-Oriented Programming (OOP). While OOP focuses on organizing code into objects and classes, AOP concerns cross-cutting - functionalities affecting multiple parts of an application, like logging, error handling, security, or transaction management.

A Simple Example

Considering that our UserService class includes login and logout methods, we need to log these events effectively. The code can be implemented straightforwardly using the traditional method:

class UserService {
	public void login() {
		// log the event
		log("User is attempting to logg in");

		// login logic
	}

	public void logout() {
		// logout logic

		// log the event
		log("User has logged out");
	}
} 

The problem is that logging is not an essential part of the login or logout methods themselves. Therefore, if there's a way to separate the logging logic from these methods, it would improve code organization. This is where AOP becomes useful:

class UserService {
	public void login() {
		// login logic
	}

	public void logout() {
		// logout logic
	}
}

// Use an aspect to handle logging
class LoggingAspect {
	@Before("execution(* UserService.login(..))")
	public void logBeforeLogin() {
		log("User is attempting to logg in");
	}

	@After("execution(* UserService.logout(..))")
	public void logAfterLogout() {
		log("User has logged out");
	}
}

Using AOP, we can separate logging from the core login and logout logic to keep the code clean. Additionally, AOP can also be utilized for other cross-cutting concerns, such as error handling and permission checks.

Full Code Using Spring

  1. UserService Class
import org.springframework.stereotype.Service;

@Service
public class UserService {
	public void login(String username) {
		System.out.println(username + " logged in.");
	}
	
	public void logout(String username) {
		System.out.println(username + " logged out.");
	}
}
  1. Logging Aspect
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {
	@Before("execution(* UserService.login(..))")
	public void logBeforeLogin(JoinPoint joinPoint) {
		String username = (String) joinPoint.getArgs()[0];
		System.out.println("Log: " + username + " is attempting to log in."); 
	}
	
	@After("execution(* UserService.logout(..))")
	public void logAfterLogout(JoinPoint joinPoint) {
		String username = (String) joinPoint.getArgs()[0];
		System.out.println("Log: " + username + " has logged out.");
	}

	@Around("execution(* UserService.login(..))")
	public void logLoginDetails(ProceedingJoinPoint joinPoint) {
		log("Login event started");

		// Process with the method execution
		joinPoit.proceed();

		log("Login event completed")
	}
}
  1. Run the application
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainApp {
	public static void main(String[] args) {
		ApplicationContext context = new AnnotationConfigApplicationContext(UserService.class, LoggingAspect.class);
		UserService userService = context.getBean(UserService.class);
	
		userService.login("Jone");
		userService.logout("Jone");
	}
}

Key Concepts in AOP

  1. Aspect: An aspect is a module that encapsulates a concern that cuts across multiple classes or methods, such as the LoggingAspect in the code above.
  2. Advice: Advice is the code that runs at a particular joint point in the program. It defines what the aspect should do and when it should do it, like the LoginDetails, and LogoutDetails methods in the code above. There are different types of advice:
    1. Before: Runs before a method executes.
    2. After: Runs after a method executes.
    3. Around: Wraps the method, allowing code to execute both before and after the method execution.
    4. After throwing: Runs only if a method throws an exception.
  3. Joint Point: A joint point is a specific point in the application where the advice can be inserted. it is usually a method. like the login and logout in the code above.
  4. Pointcut: A pointcut is an expression that selects join points where advice should be applied. It defines the criteria for matching join points. For example, the pointcut is defined within the @After annotation: @After("execution(* UserService.login(..))")
    • execution: This is a pointcut designator that specifies that the pointcut matches method execution join points.
    • *: this wildcard means that the advice applies to any return type.
    • UserService.login(..): This specifies the exact method name and parameters that the pointcut applies to. The .. means it can accept any number of parameters
  5. Weaving: Weaving is the process of applying aspects to the target code at specified join points. This can happen at different stages:
    1. Compile-time: the aspect is applied during compilation, creating a new class with the aspect woven in.
    2. Load-time: The aspect is applied when the classes are loaded into the JVM.
    3. Runtime: The aspect is applied at runtime.

More About Pointcut

Pointcut expressions can be quite powerful and flexible, enabling developers to target specific methods, classes, or even specific conditions. Below are several types of pointcut expressions and examples of how to use them.

  1. Execution pointcut: execution(modifiers-pattern? return-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
// match all methods in a specific class
execution(* comm.example.service.UserService.*(..))

// match public methods that return a specific type
execution(public String com.service.UserService.Login(..))

// match all methods that start with `set`
execution(* set*(..))
  1. Within pointcut: within(type-pattern)
// match all methods within a specific package
within(com.example.service..*)

// match all methods in a class
within(com.example.service.userService)
  1. Combiling pointcuts
// match methods that are either `login` or `logout`
execution(* login(..)) || execution(* logout(..))

// match methods in `UserService` that are NOT public
within(com.example.service.RserService) && !execution((public * *))

Conclusion

AOP significantly enhances software development by providing a systematic way to separate cross-cutting concerns. This separation not only leads to cleaner and more maintainable code but also allows developers to focus on implementing functionality without writing repetitive code for common tasks, promoting a modular design.